using System;
using System.Collections.Generic;
using System.Text;
using CommonTypes;
using CommonTypes.Math.Geometry3D ;
using CommonTypes.Math;
using CommonTypes.Events;
using System.ComponentModel;

namespace BuildingBasicDomain
{
    [Serializable ]
    public class Line : ILine, ICloneable, System .Collections .IComparer 
    {

        #region " Protected  Decleration"

        protected IPoint sNode;
        protected IPoint eNode;

        public static event CreationHandler onCreate;

        public Vector3D Direction
        {
            get
            {
                Vector3D v1 = ENode.V - SNode.V;
                // v1.Normalize();
                return v1;
            }
        }
        [Description("Element SNode"), Category("Nodes")]
        public virtual IPoint SNode
        {
            set
            {
                sNode = value;
            }

            get
            {
                return sNode;
            }
        }

        [Description("Element SNode"), Category("Nodes")]
        public virtual IPoint ENode
        {
            set
            {
                eNode = value;
            }

            get
            {
                return eNode;
            }
        }

        #endregion
        public Line()
        {
            if (onCreate != null)
                onCreate(this, new CreationEventArgs());

        }
        public Line(IPoint sNode, IPoint eNode) : this() 
        {
            System.Diagnostics.Debug.Assert(sNode != null && eNode != null);

            if (sNode != null && eNode != null)
            {
                this.SNode = sNode;
                this.ENode = eNode;
            }
           else 
                throw new DomainModelExeption("Points must not be null ");
        }

        #region "ILine Geometry implementation "

        public Vector3D Perpendicular(IPoint point)
        {
            IPoint projectionPoint = ProjectionOnLine(point);
            return point.V - projectionPoint.V;

        }


        public double Length()
        {
            System.Diagnostics.Debug.Assert(ENode != null && SNode != null);
            Vector3D v1 = ENode.V - SNode.V; return v1.GetLength();
        }

        public IPoint ProjectionOnLine(IPoint point)
        {
            System.Diagnostics.Debug.Assert(point != null);
            //MOVE THIS TO MATH LIBRARY 
            IPoint projectionPoint;
            Vector3D projection = point.V - SNode.V;
            Vector3D direction = this.Direction;
            direction.Normalize();
            projection.Normalize ();
            double cos = Vector3D.DotProduct(direction, projection);
            projection = point.V - SNode.V;
            projection = direction * (projection.GetLength() * cos) + SNode.V;
            projectionPoint = (Point)projection;
            return projectionPoint;
        }

        public IPoint IntersectsLine(ILine line, Geometry .Type_of_Intersection  typeOfIntersection)
        {
            System.Diagnostics.Debug.Assert(line != null );
           // return IntersectionLine2D(_line, typeOfIntersection);
          IPoint point  =   LineLineIntersect(this, line);
          if (typeOfIntersection == Geometry.Type_of_Intersection.On_Both && this.ContainsPointClose (point ) && line .ContainsPointClose (point))
          {
            //  bool p1 = this.ContainsPointClose(point);
           //   bool p2 = line.ContainsPointClose(point);

              return point;
          }
          else if (typeOfIntersection == Geometry.Type_of_Intersection.On_Line1 && this.ContainsPointClose(point))
              return point;
          else if (typeOfIntersection == Geometry.Type_of_Intersection.On_Line2 && line.ContainsPointClose(point))
              return point;
          else return null;
          
        }

        public double ClosestDistanceToLine(ILine line)
        {
            // dublicate code .No Matter

            const double EPS = 0.1;

            IPoint p1 = this.SNode;
            IPoint p2 = this.ENode;
            IPoint p3 = line.SNode;
            IPoint p4 = line.ENode;

            IPoint p13, p43, p21;
            double d1343, d4321, d1321, d4343, d2121;
            double numer, denom;

            double mua = 0, mub = 0;

            p13 = new Point(p1.X - p3.X, p1.Y - p3.Y, p1.Z - p3.Z);
            p43 = new Point(p4.X - p3.X, p4.Y - p3.Y, p4.Z - p3.Z);

            if (Math.Abs(p43.X) < EPS && Math.Abs(p43.Y) < EPS && Math.Abs(p43.Z) < EPS)
                return (-1);
            p21 = new Point(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);

            if (Math.Abs(p21.X) < EPS && Math.Abs(p21.Y) < EPS && Math.Abs(p21.Z) < EPS)
                return (-1);

            d1343 = p13.X * p43.X + p13.Y * p43.Y + p13.Z * p43.Z;
            d4321 = p43.X * p21.X + p43.Y * p21.Y + p43.Z * p21.Z;
            d1321 = p13.X * p21.X + p13.Y * p21.Y + p13.Z * p21.Z;
            d4343 = p43.X * p43.X + p43.Y * p43.Y + p43.Z * p43.Z;
            d2121 = p21.X * p21.X + p21.Y * p21.Y + p21.Z * p21.Z;

            denom = d2121 * d4343 - d4321 * d4321;
            if (Math.Abs(denom) < EPS)
                return (-1);
            numer = d1343 * d4321 - d1321 * d4343;

            mua = numer / denom;
            mub = (d1343 + d4321 * (mua)) / d4343;

            Vector pa = new Vector();
            Vector pb = new Vector();
            pa.X = p1.X + mua * p21.X;
            pa.Y = p1.Y + mua * p21.Y;
            pa.Z = p1.Z + mua * p21.Z;
            pb.X = p3.X + mub * p43.X;
            pb.Y = p3.Y + mub * p43.Y;
            pb.Z = p3.Z + mub * p43.Z;

            pb = pa - pb;
            return pb.Length();

        }

        /// <summary>
        /// The Line To  Line Intersection Algorithm in 3D.
        /// If You Do Not Like it Ovverde it .And Implement it .
        /// </summary>
        /// <param name="line1"></param>
        /// <param name="line2"></param>
        /// <returns></returns>
        protected virtual IPoint LineLineIntersect(ILine line1, ILine line2)  {
         const double  EPS  = 0.1;


           IPoint p1 = line1 .SNode ;
           IPoint p2 = line1 .ENode ;
           IPoint p3 = line2 .SNode ;
           IPoint p4 = line2.ENode;

            IPoint p13, p43, p21;
            double d1343, d4321 , d1321, d4343, d2121;
            double numer, denom;
   
            double mua = 0, mub = 0;

            p13 = new Point ( p1.X - p3.X,p1.Y - p3.Y,p1.Z - p3.Z);
            p43 = new Point ( p4.X - p3.X , p4.Y - p3.Y,p4.Z - p3.Z);

            if (Math.Abs(p43.X) < EPS && Math.Abs(p43.Y) < EPS && Math.Abs(p43.Z) < EPS)
                return (null);
            p21 = new Point ( p2.X - p1.X, p2.Y - p1.Y,p2.Z - p1.Z);

            if (Math.Abs(p21.X) < EPS && Math.Abs(p21.Y) < EPS && Math.Abs(p21.Z) < EPS)
                return (null);

            d1343 = p13.X * p43.X + p13.Y * p43.Y + p13.Z * p43.Z;
            d4321 = p43.X * p21.X + p43.Y * p21.Y + p43.Z * p21.Z;
            d1321 = p13.X * p21.X + p13.Y * p21.Y + p13.Z * p21.Z;
            d4343 = p43.X * p43.X + p43.Y * p43.Y + p43.Z * p43.Z;
            d2121 = p21.X * p21.X + p21.Y * p21.Y + p21.Z * p21.Z;

            denom = d2121 * d4343 - d4321 * d4321;
            if (Math.Abs(denom) < EPS)
                return (null );
            numer = d1343 * d4321 - d1321 * d4343;

            mua = numer / denom;
            mub = (d1343 + d4321 * (mua)) / d4343;

            Vector pa = new Vector();
            Vector pb = new Vector();
            pa.X = p1.X + mua * p21.X;
            pa.Y = p1.Y + mua * p21.Y;
            pa.Z = p1.Z + mua * p21.Z;
            pb.X = p3.X + mub * p43.X;
            pb.Y = p3.Y + mub * p43.Y;
            pb.Z = p3.Z + mub * p43.Z;

            pb= pa -pb;

            if (pb.Length() < EPS)
                return ((Point)pa);
            else return null;
        }

        [Obsolete ("use LineLineIntersect",true)]
        private double  dist3D_Segment_to_Segment(ILine  S1, ILine  S2)
        {
            const double  SMALL_NUM = 1.0f;

            Vector3D u = S1.ENode .V  - S1.SNode .V ;
            Vector3D v = S2.ENode .V  - S2.SNode .V ;
            Vector3D w = S1.SNode .V  - S2.SNode .V ;

            double a = Vector3D.DotProduct (u, u);        // always >= 0
            double b = Vector3D.DotProduct(u, v);
            double c = Vector3D.DotProduct(v, v);        // always >= 0
            double d = Vector3D.DotProduct(u, w);
            double e = Vector3D.DotProduct(v, w);

            double D = a * c - b * b;       // always >= 0
            double sc, sN, sD = D;      // sc = sN / sD, default sD = D >= 0
            double tc, tN, tD = D;      // tc = tN / tD, default tD = D >= 0

            // compute the _line parameters of the two closest points
            if (D < SMALL_NUM)
            { // the lines are almost parallel
                sN = 0.0;        // force using point P0 on segment S1
                sD = 1.0;        // to prevent possible division by 0.0 later
                tN = e;
                tD = c;
            }
            else
            {                // get the closest points on the infinite lines
                sN = (b * e - c * d);
                tN = (a * e - b * d);
                if (sN < 0.0)
                {       // sc < 0 => the s=0 edge is visible
                    sN = 0.0;
                    tN = e;
                    tD = c;
                }
                else if (sN > sD)
                {  // sc > 1 => the s=1 edge is visible
                    sN = sD;
                    tN = e + b;
                    tD = c;
                }
            }

            if (tN < 0.0)
            {           // tc < 0 => the t=0 edge is visible
                tN = 0.0;
                // recompute sc for this edge
                if (-d < 0.0)
                    sN = 0.0;
                else if (-d > a)
                    sN = sD;
                else
                {
                    sN = -d;
                    sD = a;
                }
            }
            else if (tN > tD)
            {      // tc > 1 => the t=1 edge is visible
                tN = tD;
                // recompute sc for this edge
                if ((-d + b) < 0.0)
                    sN = 0;
                else if ((-d + b) > a)
                    sN = sD;
                else
                {
                    sN = (-d + b);
                    sD = a;
                }
            }
            // finally do the division to get sc and tc
            sc = (Math .Abs (sN) < SMALL_NUM ? 0.0 : sN / sD);
            tc = (Math.Abs(tN) < SMALL_NUM ? 0.0 : tN / tD);

            // get the difference of the two closest points
            Vector3D dP = w + (sc * u) - (tc * v);  // = S1(sc) - S2(tc)

            return (dP.GetLength ());   // return the closest distance
        }

        [Obsolete ("Use the general 3D case , TERE IS NO SERIOUS GEOMETRY LIBRARY IN  ",true )]
        private IPoint IntersectionLine2D(ILine line, Geometry .Type_of_Intersection  typeOfIntersection)
        {
            if (!line.IsHorizontal()) throw new NotImplementedException("Interesection Supported only for 2D");
            // TODO : Extednd this to 3D _line intersection 
            double secx; double secy;
            IPoint intersection = null;

            Geometry.Type_of_Intersection result = Geometry.E_SgmSec(line.SNode.X, line.SNode.Z, line.ENode.X, line.ENode.Z,
            this.SNode.X, this.SNode.Z, this.ENode.X, this.ENode.Z, out secx, out secy);
            if (result != Geometry .Type_of_Intersection .On_Both  || result != Geometry .Type_of_Intersection .Prallel_segments )
            {
                if (typeOfIntersection == Geometry.Type_of_Intersection.On_Segment_Extent && result == Geometry.Type_of_Intersection.On_Segment_Extent)
                {
                    intersection = new Point(secx, this.SNode.Y, secy);
                }
                else if ((typeOfIntersection == Geometry.Type_of_Intersection.On_SomethingMine_I_Realy_do_Not_remenber_in_this_fucking_mess && result == Geometry.Type_of_Intersection.On_Line1) || (typeOfIntersection == Geometry.Type_of_Intersection.On_SomethingMine_I_Realy_do_Not_remenber_in_this_fucking_mess && result == Geometry .Type_of_Intersection .On_Line2 ))
                    intersection = new Point(secx, this.SNode.Y, secy);
            }
            return intersection;
        }

        public IPoint IntersectsPlane(IPlane plane) { throw new NotImplementedException (); }

        /// <summary>
        /// check  if point falls in line .Not including snode , enode 
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public bool ContainsPointOpen(IPoint point)
        {
            try
            {
     

                Vector3D vector = point.V - SNode.V;
                if (Vector3D.CrossProduct(vector, Direction).GetLength() == 0)
                {
                    
                    Vector3D t1 = (point.V - SNode.V);
                    Vector3D t2 = ENode.V - point.V;
                    double errordistance = (t1.GetLength() + t2.GetLength())
                        - Direction.GetLength();
//                    Vector3D t3 = (SNode.V -point.V );
//                    Vector3D t4 = point.V - ENode.V  ;
                    if (Math.Abs(errordistance) < 0.01 && Math .Abs (t1.GetLength ())>0.01 && Math .Abs (t2.GetLength ())>0.01 )
                        return true;
                    else
                        return false;
                }
            }
            catch  {}
            return false;
        }

        /// <summary>
        /// check  if point falls in line .including snode , enode 
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public bool ContainsPointClose(IPoint point)
        {
            if (point == null) return false;
            if ((point.distancePoint(SNode)) < 0.01 || (point.distancePoint(ENode) < 0.01))
                return true;
            return ContainsPointOpen(point);
        }

        public double DistancePoint(IPoint point)
        {
            Vector3D projection = Perpendicular(point);
            return projection.GetLength();
        }

        #endregion

        #region "General Decleration "

        /// <summary>
        /// Method IsVertical
        /// </summary>
        /// <returns>A bool</returns>
        public bool IsVertical()
        {
            double error = Math.Abs (Vector3D.CrossProduct(Direction, new Vector3D(0, 1, 0)).GetLength());
            if (error  <= 100)
                return true;
            return false;
        }

        /// <summary>
        /// Method IsHorizontal
        /// </summary>
        /// <returns>A bool</returns>
        public bool IsHorizontal()
        {
            if (Vector3D.DotProduct(Direction, new Vector3D(0, 1, 0)) == 0)
                return true;
            return false;
        }

        #endregion

        #region " Ovverides decleration "

        public static bool operator ==(Line line1, Line line2)
        {
            object o1 = line1;
            object o2 = line2;

            if (o1 == null && o2 == null) return true;
            if (o1 == null) return false;
            if (o2 == null) return false;

            if (hasSameNodes(line1, line2))
                return true;
            else
                return false;

        }

        private static bool hasSameNodes(Line line1, Line line2)
        {
            bool val = (Point)line1.ENode == (Point)line2.ENode && (Point)line1.SNode == (Point)line2.SNode;

            return val;
        }

        public static bool operator !=(Line line1, Line line2)
        {
            return !(line1 == line2);
        }

        public override bool Equals(object obj)
        {
            if (obj is Line)
                return (bool)(this == (Line)obj);
            else return false;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public Object Clone()
        {
            if ((SNode is Point) && (ENode is Point))
                return new Line((Point)SNode, (Point)ENode);
            return null;

        }

        #endregion

        #region ILine Members

        public double[] getAngles()
        {
            Vector3D direction = this.Direction;
            direction.Normalize();
            double[] angles = new double[3];
            
            angles[0] = Extractangle(direction, angles,new Vector3D(1, 0, 0));
            angles[1] = Extractangle(direction, angles, new Vector3D(0, 1, 0));
            angles[2] = Extractangle(direction, angles, new Vector3D(0, 0, 1));
            return angles;
        }

        private double  Extractangle(Vector3D direction, double[] angles, Vector3D vector)
        {
            double dot = Vector3D.DotProduct(direction, vector);

            dot = System.Math.Acos(dot);

            dot = dot * 360 / (System.Math.PI * 2);

            return dot;
            
        }



        #endregion

        #region IComparer<IPoint> Members

        int IComparer<IPoint> .Compare(IPoint x, IPoint y)
        {

                if (x.distancePoint(this.sNode) >= y.distancePoint(this.sNode))
                    return 1;
                else return -1;

        }
        #region IComparer Members

        public int Compare(object x, object y)
        {
            if (x is IPoint && y is IPoint)
            {
                if (((Point)x).distancePoint((Point)this.sNode) >= ((Point)y).distancePoint((Point)this.sNode))
                    return 1;
                else return -1;
            }
            else return 0;
        }

        #endregion
        public IList<IPoint> GetEdges()
        {
            IList<IPoint> points = new List<IPoint>();

            points.Add(sNode);
            points.Add(eNode);

            return points;
        }

        public IPoint GetMiddle()
        {
            return new Point((this.sNode.X + this.eNode.X) / 2, (this.sNode.Y + this.eNode.Y) / 2,
                (this.sNode.Z + this.eNode.Z) / 2);
        }
        #endregion

        /// <summary>
        /// return the point on line to distance
        /// </summary>
        /// <param name="dist"></param>
        /// <returns></returns>
        public IPoint GetPointToDistance(double dist)
        {
            if (dist <= 0 || dist >= 1) return null;
            Vector3D vect = this.Direction* dist;;
            vect = vect + this.SNode.V ;
            IPoint point = (Point)vect;
            return point;
        }
        /// <summary>
        /// get the distance of point if on line .
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public double DistenceToSatrtNode(IPoint point)
        {
            if (this.ContainsPointOpen(point))
                return point.distancePoint(sNode);
            else return -1;
        }



    }


}
